#!/bin/bash -eu
#
# Copyright 2020 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

source /opt/c2d/c2d-utils || exit 1

export ES_PATH_CONF=/etc/elasticsearch

readonly es_pwd_file="/opt/c2d/passwords"

#######################################
# Checks if a host and port are opened
# Arguments:
#   Host name
#   Port number
#######################################
function wait_for_port() {
  local HOST="$1"
  local PORT="$2"
  timeout --preserve-status 300 bash -c "until echo > /dev/tcp/${HOST}/${PORT}; do sleep 2; done"
  if [[ "$?" -ne 0 ]]; then
      exit 1
  fi
}

#######################################
# Waits for readiness on the specified number of nodes
# and green status on the Elasticsearch health endpoint.
# Arguments:
#  Number of expected nodes to wait for.
#  Authentication password. Optional.
# Outputs:
#  Writes statuses to stdout.
#######################################
function wait_for_green_elastic_cluster() {
  local -r protocol="$1"
  local -r hostname="$2"
  local -r nodes="$3"

  local -r health_url="${protocol}://${hostname}:9200/_cluster/health?format=yaml"
  local -r nodes_health_url="${health_url}&filter_path=number_of_nodes"
  local -r status_health_url="${health_url}&filter_path=status"

  # If no password specified, don't leave auth_params empty
  local -a auth_params=()
  if [[ "$#" -eq 4 ]]; then
    local -r password="$4"
    auth_params=(-u "${es_default_username}:${password}")
  fi

  # Wait for exact number of nodes
  until curl -k -s "${auth_params[@]}" "${nodes_health_url}" | grep "${nodes}"; do
    echo "Waiting for ${nodes} nodes in cluster..."
    sleep 5
  done

  # Wait for status 'green'
  until curl -k -s "${auth_params[@]}" "${status_health_url}" | grep green; do
    echo "Waiting for green status in cluster.."
    sleep 3
  done

  echo "Cluster ready."
}

#######################################
# Changes user and password at an Elasticsearch Server.
# Arguments:
#  Hostname endpoint.
#  Authentication username to the API.
#  Authentication password to the API.
#  Username to change the password.
#  Password to be changed.
#######################################
function change_user_password() {
  local -r hostname="$1"
  local -r protocol="$2"
  local -r auth_user="$3"
  local -r auth_user_pass="$4"
  local -r user_to_change="$5"
  local -r new_pass="$6"

  local -r changepass_url="${protocol}://${hostname}:9200/_security/user/${user_to_change}/_password"
  local response=""

  echo "Changing ${user_to_change} password..."

  response="$(curl -k -X POST \
    -u "${auth_user}:${auth_user_pass}" \
    -d "{\"password\":\"${new_pass}\"}" \
    -H "Content-Type: application/json" \
    "${changepass_url}")"

  if [[ "${response}" != "{}" ]]; then
    echo "Password change failed: ${response}"
    exit 1
  fi
}

#######################################
# Generate a config file based on a template using environment variables.
# Arguments:
#  Directory where template and config files are located.
#  Template filename.
#  Generated filename
#######################################
function fill_in_config_template() {
  local -r base_dir="$1"
  local -r template_file="$2"
  local -r original_file="$3"

  cd "${base_dir}"
  mv "${original_file}" "${original_file}.orig"
  envsubst < "${template_file}" > "${original_file}"
  rm "${template_file}"
}

#######################################
# Auto generate default passwords for all
# built-in users in a secured Elasticsearch server.
# Passwords will be generated to /opt/c2d/passwords.
# https://www.elastic.co/guide/en/elasticsearch/reference/current/setup-passwords.html
# https://www.elastic.co/guide/en/elasticsearch/reference/current/built-in-users.html
#######################################
function autogenerate_passwords() {
  local protocol="$1"

  /usr/share/elasticsearch/bin/elasticsearch-setup-passwords auto -b -u "${protocol}://localhost:9200" \
    | grep "PASSWORD" > "${es_pwd_file}"
}

#######################################
# Remove generated passwords file.
#######################################
function remove_pwd_file() {
  rm -f "${es_pwd_file}"
}

#######################################
# Get a pregenerated password according username requested.
# Arguments:
#  Elasticsearch username.
# Outputs:
#  Password requested
#######################################
function get_autogenerated_password() {
  local -r service_type="$1"
  cat "${es_pwd_file}" \
    | grep -P "^.*(${service_type})\s=\s(.*)$" \
    | awk -F '=' '{ print $2 }' | tr -d ' '
}

#######################################
# Build a json string with the nodes received
# Arguments:
#  String with nodes separated by spaces.
# Outputs:
#  String with nodes enclosed by quotes,
#  delimited by comma
#######################################
function build_seed_hosts() {
  local -r nodes="$1"
  local es_nodes_json=""

  for node in ${es_nodes}; do
    es_nodes_json+="\"${node}\" "
  done
  echo "${es_nodes_json}" | sed 's/ /,/g'
}

#######################################
# Generate instance.yaml to be used on elasticsearch-certutil
# https://www.elastic.co/guide/en/elasticsearch/reference/current/certutil.html#certutil-silent
# Arguments:
#  Elasticsearch nodes as a string separated by spaces.
#  Logstash instance name
#  Kibana instance name
# Outputs:
#  YAML content to be used on elasticsearch-certutil
#######################################
function generate_certutil_config() {
  local -r nodes=( $(echo "$1") )
  local -r logstash_node="$2"
  local -r kibana_node="$3"
  local -r current_project="$(get_project_id)"
  local node_idx=0

  printf "instances:\n"
  for node in "${nodes[@]}"; do
    printf -- "- name: node${node_idx}\n"
    printf -- "  dns: [\"${node}.c.${current_project}.internal\"]\n"
    node_idx=$((node_idx+1))
  done

  if [[ ! -z "${logstash_node}" ]]; then
    printf -- "- name: logstash\n"
    printf -- "  dns: [\"${logstash_node}.c.${current_project}.internal\"]\n"
  fi

  if [[ ! -z "${kibana_node}" ]]; then
    printf -- "- name: kibana\n"
    printf -- "  dns: [\"${kibana_node}.c.${current_project}.internal\"]\n"
  fi
}

#######################################
# Apply SSL settings to the Elasticsearch config file
# Arguments:
#  Elasticsearch configuration filepath
#  Node index
#######################################
function apply_secure_config() {
  local config_file="$1"
  export current_node_idx="$2"

  local patch_file="/opt/c2d/patch-ssl"
  local https_config="$(cat ${patch_file} | envsubst)"
  echo "${https_config}" >> "${config_file}"
}

#######################################
# Generate certificates using elasticsearch-certutil utility
# Arguments:
#  Temporary folder where certificates will be generated
#  Elasticsearch nodes as a string separated by spaces
#  Logstash instance name
#  Kibana instance name
#######################################
function generate_certificates() {
  local -r tmp_certs_folder="$1"
  local -r elasticsearch_nodes="$2"
  local -r logstash_node="$3"
  local -r kibana_node="$4"

  # Configure folder
  echo "Creating tmp certificates folder..."
  mkdir -p "${tmp_certs_folder}"

  echo "Generating certificates configuration..."
  # Generate config for elasticsearch-certutil
  generate_certutil_config "${elasticsearch_nodes}" "${logstash_node}" "${kibana_node}" \
    > "${tmp_certs_folder}/instance.yaml"

  echo "Generating certificates using elasticsearch-certutil..."
  # Generate certificates
  /usr/share/elasticsearch/bin/elasticsearch-certutil cert \
    --keep-ca-key \
    --pem \
    --in "${tmp_certs_folder}/instance.yaml" \
    --out "${tmp_certs_folder}/certs.zip"

  echo "Extracting certificates created..."
  # Unzip certificates to files folder
  unzip "${tmp_certs_folder}/certs.zip" -d "${tmp_certs_folder}/files"
  echo "Certificates created successfully."
}

#######################################
# Copies certificate from temporary folder to
# the Elasticsearch folder
# Arguments:
#  Folder where all certificates are stored
#  Master node index
#######################################
function apply_certificate_master_node() {
  local -r tmp_certs_folder="$1"
  local -r cert_folder="$2"
  local -r current_node_idx="$3"

  mkdir -p "${cert_folder}"
  cp ${tmp_certs_folder}/files/ca/*.* ${cert_folder}
  cp ${tmp_certs_folder}/files/node${current_node_idx}/*.* ${cert_folder}
}

#######################################
# Download a certificate from Certshare Service
# Arguments:
#  Target folder path where certificate should be saved
#  Certshare Service Host
#  Certificate name
#######################################
function download_certificate() {
  local -r cert_folder="$1"
  local -r certshare_host="$2"
  local -r cert_name="$3"

  for ext in crt key; do
    curl -s -o "${cert_folder}/${cert_name}.${ext}" \
      "http://${certshare_host}:8000/${cert_name}/${cert_name}.${ext}"
  done
}

#######################################
# Download certificates required for a specific
# Elasticsearch node
# Arguments:
#  Target folder path where certificate should be saved
#  Certshare Service Host
#  Node index
#######################################
function download_certificates() {
  local -r cert_folder="$1"
  local -r certshare_host="$2"
  local -r node_idx="$3"

  mkdir -p "${cert_folder}"
  download_certificate "${cert_folder}" "${certshare_host}" "ca"
  download_certificate "${cert_folder}" "${certshare_host}" "node${node_idx}"
}
